/*
 * Copyright 2007 Ulf Ochsenfahrt <ulf@ofahrt.de>
 *
 * This file is part of mnemoj, a software transactional memory implementation
 * that is based on Project Darkstar.
 *
 * mnemoj is free software: you can redistribute it
 * and/or modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation and
 * distributed hereunder to you.
 *
 * Project Darkstar Server is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.ofahrt.mnemoj.impl;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.NoSuchElementException;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicLong;

import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.ObjectNotFoundException;
import com.sun.sgs.app.Task;

public final class MemoryStorage implements Storage
{

private final AtomicLong entryIds = new AtomicLong();
private final ConcurrentSkipListMap<Long,ManagedEntry> data = new ConcurrentSkipListMap<Long,ManagedEntry>();
private final ConcurrentSkipListMap<String,Binding> bindings = new ConcurrentSkipListMap<String,Binding>();

private final AtomicLong currentTaskId = new AtomicLong();
private final ConcurrentSkipListSet<ManagedTask> tasks = new ConcurrentSkipListSet<ManagedTask>();

private long currentTime = 0;
private final Object timeLock = new Object();

public MemoryStorage()
{
	// Ok
}

public ManagedEntry getEntry(Long id)
{
	ManagedEntry entry = data.get(id);
	if (entry == null) throw new ObjectNotFoundException();
	entry.getLock().lock();
	return entry;
}

public ManagedEntry createEntry()
{
	Long id = Long.valueOf(entryIds.incrementAndGet());
	ManagedEntry entry = new ManagedEntry(id);
	entry.getLock().lock();
	return entry;
}


public Binding getBinding(String id)
{
	Binding binding = bindings.get(id);
	if (binding == null)
		throw new NameNotBoundException(id);
	binding.getLock().lock();
	return binding;
}

public Binding createBinding(String id)
{
	Binding binding = bindings.get(id);
	if (binding == null)
	{
		binding = new Binding(id);
		Binding b = bindings.putIfAbsent(id, binding);
		if (b != null) binding = b;
	}
	binding.getLock().lock();
	return binding;
}

public void commit(Transaction transaction)
{
	long time = getCurrentTime();
	
	IdentityHashMap<ManagedEntry,byte[]> convertedEntries = new IdentityHashMap<ManagedEntry,byte[]>();
	for (Entry<ManagedEntry,ManagedObject> e : transaction.instancedEntries.entrySet())
	{
		ManagedEntry entry = e.getKey();
		ManagedObject value = e.getValue();
		if (value != null)
			convertedEntries.put(entry, SerializationHelper.serialize(value));
	}
	
	ArrayList<ManagedTask> convertedTasks = new ArrayList<ManagedTask>();
	for (TaskEntry e : transaction.tasks)
	{
		long attime = time+e.getDelay();
		long id = currentTaskId.incrementAndGet();
		while (id >= Long.MAX_VALUE/2)
		{
			currentTaskId.compareAndSet(id, 0);
			id = currentTaskId.incrementAndGet();
		}
		Task task = e.getTask();
		if (task instanceof ManagedObject)
		{
			ManagedObject mo = (ManagedObject) task;
			convertedTasks.add(new ManagedTask(attime, id, transaction.knownObjects.get(mo)));
		}
		else
			convertedTasks.add(new ManagedTask(attime, id, SerializationHelper.serialize(task)));
	}
	
	// no exception may happen after this point
	// starting at this point, the in-memory storage structures are manipulated
	for (Entry<ManagedEntry,ManagedObject> e : transaction.instancedEntries.entrySet())
	{
		ManagedEntry entry = e.getKey();
		if (e.getValue() == null)
		{
			data.remove(entry.getId());
			entry.set(null);
		}
		else
		{
			if (entry.isNew())
				data.put(entry.getId(), entry);
			entry.set(convertedEntries.get(entry));
		}
		entry.getLock().unlock();
	}
	
	for (Entry<Binding,ManagedReferenceImpl> e : transaction.instancedBindings.entrySet())
	{
		Binding binding = e.getKey();
		ManagedReferenceImpl ref = e.getValue();
		binding.set(ref);
		binding.getLock().unlock();
	}
	
	for (ManagedTask e : convertedTasks)
		tasks.add(e);
}

public void revert(Transaction transaction)
{
	for (ManagedEntry entry : transaction.lockedEntries.values())
		entry.getLock().unlock();
	for (Binding binding : transaction.lockedBindings.values())
		binding.getLock().unlock();
}

public long getCurrentTime()
{
	synchronized (timeLock)
	{ return currentTime; }
}

public void advanceTime(long totime)
{
	try
	{
		while (true)
		{
			ManagedTask firstTask = tasks.first();
			long taskTime = firstTask.getTime();
			if (taskTime >= totime) return;
			if (!firstTask.getLock().tryLock())
				return;
			synchronized (timeLock)
			{ currentTime = taskTime; }
			Transaction transaction = new Transaction(this, this.getCurrentTime());
			ManagerContext context = new ManagerContext(transaction);
			AppContextDispatcher.call(context, firstTask);
			tasks.remove(firstTask);
		}
	}
	catch (NoSuchElementException e)
	{/*OK*/}
}

}
